Research
Security News
Threat Actor Exposes Playbook for Exploiting npm to Build Blockchain-Powered Botnets
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
@starbeam/timeline
Advanced tools
`@starbeam/timeline` is part of Starbeam, a library for building and using reactive objects in any framework.
@starbeam/timeline
is part of Starbeam, a library for building and using reactive objects in any framework.
@starbeam/timeline
is stable, with the same semver policy as Starbeam.
That said, it is not intended to be used directly by application code. Rather, it is one of the core parts of the Starbeam composition story. You can use it to better understand how Starbeam works, or to build your own Starbeam libraries.
📙 Philosophy
Higher-level libraries like
@starbeam/universal
build on lower-level primitives. These are not privileged internal APIs, and they are not marked as unstable. We believe that you, the people building Starbeam's library ecosystem, are just as innovative as Starbeam's creators. We avoid including "for me but not for thee" APIs in our composition abstractions. Go forth and build!
At a fundamental level, Starbeam reactivity is made up of mutation events that happen to a data universe at a point on a timeline.
The data universe is broken up into two kinds of cells: data cells and formulas.
The Starbeam reactivity system is a perpetual cycle between two phases: Action and Render. These phases run in a cycle for as long as the program is running.
Code in the Action phase is quite powerful. It can mutate data cells as much as it wants, and it can immediately get the up-to-date values of formulas. Code in the Action phase can also read from the rendered output.
In exchange for all of that power, code in the Action phase cannot directly write to the rendered output, and it will need to wait until the next Action phase to see how the mutations to the data universe reflected onto the rendered output.
Code in the Render phase is considerably less powerful. It may read from the data universe and write to the rendered output, but it may not write to the data universe.
When using Starbeam to reflect the data universe into a Browser DOM, rendering involves multiple iterations of the Action / Render cycle. We call the entirety of this process the Rendering Process.
These steps allow you to implement framework-agnostic [resources] that can correctly use the DOM as data source. They are universal, which means that you can write code in terms of Starbeam's APIs, and it will run inside of the framework of your choice with a Starbeam adapter.
📒 Note
Each of these three steps is an Action / Render cycle. During the Action phase, application code can mutate the data universe and read from the DOM. During the Render phase, your framework will update the DOM from the changes you made during the Action phase. Typically, the three cycles of the rendering phase happen in quick succession, but you should not rely on this. Your framework may choose to do other work between the phases, and modern frameworks commonly do so in order to provide an optimal experience for your users.
Also, while application code will typically have an opportunity to run inside of each step of the Rendering Process, your framework may choose to [deactivate] or [unmount] the component before the Ready step. If application code sets up some state that needs to be torn down, it should not rely on the Measurement or Ready steps running. Instead, finalizers registered with an appropriate lifetime (see below) are guaranteed to run even if the Measurement or Ready steps do not.
In practice, these considerations are bundled together into the high-level "Stateful Formula" construct provided by @starbeam/reactive.
The Timeline
in @starbeam/timeline
coordinates these phases.
It starts out in the Actions phase, which allows free access to the data universe. As soon as a data cell in the data universe is mutated, the Timeline
schedules a Render phase using the configured scheduler. By default, this will schedule a Render phase during a microtask checkpoint, which occurs asynchronously, but before the next paint.
The Timeline
can be configured with a Coordinator
(see @starbeam/schedule), which controls the exact details of the timeline's timing.
The default behavior automatically schedules the next Render phase using a microtask checkpoint, which means that it will happen asynchronously, but before the next time the browser paints the page. The purpose of the Coordinator
is to allow you to make multiple mutations to the data universe before a Render phase occurs.
You can specify a custom Coordinator
to use an alternative strategy. For example, if you are writing a single-file demo, the entire file will finish running before a microtask checkpoint. You could create an API to use a single-file demo that automatically schedules Render phases at appropriate times.
Finally, you can also explicitly schedule a Render phase, which will supersede the Coordinator
's policy and simply wait until you're ready to render.
import { TIMELINE } from "@starbeam/timeline";
import { Cell } from "@starbeam/reactive";
const person = reactive({
id: null,
name: "@tomdale",
});
const name = Cell("Tom");
const userId = Cell(null);
function multiStepProcess(name, url) {
const render = TIMELINE.manualRenderPhase();
person.name = name;
fetch(url).then((data) => {
person.id = data.id;
render();
});
}
The Timeline
is a representation of discrete time, where each mutation to a data cell is given a unique, monotonically increasing timestamp.
Every time you mutate a data cell, the
Timeline
assigns increments the "current timestamp" by1
, and assigns that timestamp to the mutation."Discrete time" just means that there are specific points in time that we are interested in, and that "nothing interesting" happens in between those points.
When you are in an Action phase, this happens automatically.
On the other hand, Render phases are frozen in time. They may not move the timeline forward. In practice, this means that formulas are read-only and may not mutate the data universe.
💡 Note
This has nothing to do with the location of callbacks in the code. For example, it is quite normal for event handlers to occur inside initialization (the code that computes the initial state of the DOM from the data universe). However, the event handlers do not run during initialization, but rather at a later time, in response to hardware events triggered by the user.
By definition, such events happen in the Action phase, even though the function that they call was created during initialization.
TODO: Describe the validation process
TODO: Describe how to subscribe to changes in a formula
As we discussed, the timeline describes changes in the data universe and helps a consumer coordinate the two-phase process of reflecting the data universe onto the output. Both data cells and formulas are pure data: they can be automatically cleaned up by the garbage collector when nobody retains a reference to them.
On the other hand, you may encounter objects in the real world that require you to tear them down when you're done using them, and you may want to convert those objects into data in the data universe. That's where the structured finalizer comes in.
The structured finalizer allows you to set up a stateful connection to some external data, such as a WebSocket, ResizeObserver or even a fetch
request, associate it with an owner, and automatically finalize the connection when the owner is finalized.
For example, a component may set up a ResizeObserver to keep track of the size of one of the elements it creates. When the component is deactivated, the component wants to finalize the ResizeObserver
so that it doesn't leak.
Starbeam uses the structured finalizer approach to make finalization composable. Instead of making the component responsible for setting up the ResizeObserver
and specifying how to finalize the ResizeObserver
when the component it finalized, it can delegate responsibility to an ElementSize
resource:
ResizeObserver
.ResizeObserver
into data in the data universe.Let's see how this all fits together. We'll use the resource pattern from @starbeam/reactive
to create an ElementSize
resource.
import { Resource } from "@starbeam/reactive";
export function ElementSize(element: Element) {
return Resource((resource) => {
const size = reactive(getSize(element));
const observer = new ResizeObserver();
observer.observe(element, () => {
const { width, height } = getSize(element);
size.width = width;
size.height = height;
});
resource.on.finalize(() => observer.disconnect());
return size;
});
}
function getSize(element: Element) {
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
};
}
Let's look at it one piece at a time.
First, we create a vanilla getSize
function to get the width and heigh from an element.
function getSize(element: Element) {
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
};
}
Next, we create a function that will take an element and set up the resource.
export function ElementSize(element: Element) {
return Resource((resource) => {
// ...
});
}
This function operates on a fixed element, such as the top-level element of a component. The function calls the Resource
function, the built-in constructor for the resource pattern. Let's see how it works.
First, we create a reactive object with the element's width and height.
const size = reactive(getSize(element));
Next, we create a ResizeObserver
and observe the element.
const observer = new ResizeObserver();
observer.observe(element, () => {
const { width, height } = getSize(element);
size.width = width;
size.height = height;
});
When the ResizeObserver
fires, we update the width
and height
properties of the reactive object. Importantly, the ResizeObserver
's callback runs in the Action phase, like all asynchronous callbacks invoked by the browser. This means that we can freely mutate anything in the data universe. Any part of the rendered output that cares about the reactive object will run in the next Render phase, which Starbeam will automatically schedule.
Ok, that's great, ResizeObserver
requires us to disconnect from it when we no longer need it. If we don't disconnect, the observer will leak. No problem! That's the whole point of the Resource
API. Let's tell Starbeam what to do when the resource is finalized.
resource.on.finalize(() => observer.disconnect());
This code is not responsible for attaching the
ElementSize
resource to any particular owner. That will happen inside the framework adapters, which know how to turn your framework's concept of component into a Starbeam owner.
return size;
Finally, we return the size
object. The Resource
function returns an object with an owner()
method on it, which the caller can use to link the resource to an owner. The owner()
method returns the object with reactive width and height properties.
Resource
Interfaceinterface Resource<T> {
owner(parent: object): T;
}
Once linked, ElementSize
is a regular formula that can be used as part of other formulas.
Starbeam's framework adapters provide a way to attach a function that takes an Element (called an "element modifier") to an element when the framework has created it using framework-specific APIs.
For example, you would use the ref
API to attach a modifier in React, while you would use the use:
directive syntax to attach a modifier in Svelte. Check out the framework-specific documentation for more details.
If all of this is getting too abstract, let's take a look at how you would actually use ElementSize
in React.
function Box({ children }) {
return useReactiveElement((element) => {
const div = ref(HTMLDivElement);
const size = element.useModifier(div, ElementSize);
return () => (
<>
{size.match({
rendering: () => null,
attached: (size) => `${size.width}x${size.height}`,
})}
<div ref={div}>{children}</div>
</>
);
});
}
@starbeam/react
provides a React-specific way to create a ref
to put into your JSX, and then use the ElementSize
modifier with that ref
. @starbeam/react
takes care of interacting with React to get the element, and invokes the ElementSize
modifier once the element is in the DOM.
Since the React ref
API requires you to complete a full render cycle in order to attach the ref, the useModifier
API in @starbeam/react
returns a value that can either be rendering
, because it's the first render, or attached
, once the element is in the DOM. You can use the match
API to decide what to do on the first render.
Critically, while the useReactiveElement
, ref
and useModifier
APIs come from @starbeam/react
, they interact with the universal ElementSize
modifier that we wrote without having to know anything about React at all.
TODO: Describe the difference between a general concept of "lifecycle hooks", as presented by other frameworks, and how we think about the interaction between phasing and finalization in Starbeam.
FAQs
`@starbeam/timeline` is part of Starbeam, a library for building and using reactive objects in any framework.
We found that @starbeam/timeline demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
A threat actor's playbook for exploiting the npm ecosystem was exposed on the dark web, detailing how to build a blockchain-powered botnet.
Security News
NVD’s backlog surpasses 20,000 CVEs as analysis slows and NIST announces new system updates to address ongoing delays.
Security News
Research
A malicious npm package disguised as a WhatsApp client is exploiting authentication flows with a remote kill switch to exfiltrate data and destroy files.